package hu.arguscab.ai;

import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * It could generate Fitness Value from <code>FitnessFunction</code>s.
 * @author sharp
 */
public final class Fitnessor {

	Logger log = LoggerFactory.getLogger(Fitnessor.class);

	/**
	 * It holds the <code>FitnessFunction</code> itself and its quotient.
	 * Quotient is needed for mapping Fitness Values into same interval.
	 * @author bbodi
	 *
	 */
	public final class FunctionHolder {
		final private FitnessFunction func;
		final private float				q;			// quotient

		FunctionHolder( FitnessFunction func, int min, int max ) {
			assert func.getMaxValue() > func.getMinValue();
			
			this.func = func;
			q = Math.abs(((float)(max - min)) / (func.getMaxValue() - func.getMinValue()));
		}

		public FitnessFunction getFunc() {
			return func;
		}

		public float getQ() {
			return q;
		}

	}

	/**
	 * The minimum value that this <code>Fitnessor</code> could return.
	 */
	final private int minValue;

	/**
	 * The maximum value that this <code>Fitnessor</code> could return.
	 */
	final private int maxValue;
	
	/**
	 * List of <code>FitnessFunction</code>s that are used by this Fitnessor.
	 */
	private List<FunctionHolder> funcs;
	
	/**
	 * TODO: What the fucking hell is that?
	 */
	private List<FitnessFunction> tmpFitness;

	/**
	 *
	 * @param fs
	 * @param min
	 * @param max
	 */
	public Fitnessor(List<FitnessFunction> fs, int min, int max) {
		assert max > min;
		
		minValue = min;
		maxValue = max;
		funcs = new ArrayList<FunctionHolder>();
		tmpFitness = new ArrayList<FitnessFunction>();
		int i = 0;
		for ( FitnessFunction func : fs ) {
			FunctionHolder fh = new FunctionHolder( func, minValue, maxValue );
			funcs.add(fh);
			tmpFitness.add(func);
			++i;
		}
	}

	public float calcF(Model m) {
		float F = 0;
		for ( FunctionHolder fh : funcs ) {
			float w = fh.getFunc().getWeight();
			// F = [ f + abs(fmin) ]*k
			float f = fh.getFunc().calcF(m);
			//log.info("FitnessFunction({}): {}", fh.getFunc(), f);
			assert (int)f >= fh.getFunc().getMinValue() : fh.getFunc().toString()+": " + f+" < "+fh.getFunc().getMinValue();
			assert (int)f <= fh.getFunc().getMaxValue() : fh.getFunc().toString()+": " + f+" > "+fh.getFunc().getMaxValue();
			f += Math.abs(fh.getFunc().getMinValue());
			f *= w;
			F += (fh.getQ()*f);
		}
		return F;
	}

	public void addFitnessFunction(FitnessFunction func) {
		if ( getFunctionHolder(func) == null ) {
			FunctionHolder fh = new FunctionHolder( func, minValue, maxValue );
			funcs.add( fh );
			tmpFitness.add(func);
		}
	}

	public void delFitnessFunction(FitnessFunction func) {
		FunctionHolder fh = getFunctionHolder(func);
		if ( fh != null ) {
			funcs.remove( fh );
			tmpFitness.remove(func);
		}
	}

	private FunctionHolder getFunctionHolder(FitnessFunction f) {
		for ( FunctionHolder fh : funcs ) {
			if ( fh.getFunc().getClass().equals(f.getClass()) )
				return fh;
		}
		return null;
	}

	public void setFitnessFunctions(List<FunctionHolder> funcs) {
		this.funcs = funcs;
	}

	public List<FitnessFunction> getFitnessFunctions() {
		return tmpFitness;
	}
}
